const Metadata = require("../lib/metadata");
const { excuteStyle, getMappedPath, isFunction, parseStyle } = require("../lib/helper");
const { ROOTELEMENTNAME, VIEWTAG, PREREMOVED } = require("../lib/const");
const view = require("../view");
const { element } = require("./toolbar/teminal");

class ExtendModule {
	constructor(context, manager) {
		this._context = context;
		this._manager = manager;
	}

	get context() {
		return this._context;
	}

	get manager() {
		return this._manager;
	}

	_formatData() {
		let date = new Date(), info = {
			year: date.getFullYear(),
			month: date.getMonth() + 1,
			day: date.getDate(),
			hour: date.getHours(),
			minus: date.getMinutes(),
			second: date.getSeconds()
		};
		return `${info.year}-${info.month}-${info.day} ${info.hour}:${info.minus}:${info.second}`;
	}

	getAllModulesInPage() {
		return [...this.context.document.querySelectorAll("[data-module]")];
	}

	setViewPreremoveState(view) {
		view[PREREMOVED] = true;
		if (view.getChildren) {
			view.getChildren().forEach(child => {
				this.setViewPreremoveState(child);
			});
		}
	}

	getViewsInModule(moduleName) {
		let views = [];
		this.context.loader.moduleManager.scanClass((_, module) => {
			let info = Metadata.getMetadataExtends("view", module.prototype);
			info && info.module === moduleName && views.push(module);
		});
		return views;
	}

	getViewsInPageByType(type) {
		let context = this.context, r = [];
		this.getAllModulesInPage().forEach(element => {
			let view = element[VIEWTAG];
			view instanceof type && view.context.name === context.name && r.push(view);
		});
		return r;
	}

	getViewsByTemplateName(templateName) {
		let r = [];
		this.context.loader.moduleManager.scanClass((_, module) => {
			let info = { ...Metadata.getMetadataExtends("view", module.prototype) };
			info && info.template && info.template.path === templateName && r.push(...this.getViewsInPageByType(module));
		});
		return r;
	}

	getViewsByStyleName(styleName) {
		let r = [];
		this.context.loader.moduleManager.scanClass((_, module) => {
			let info = { ...Metadata.getMetadataExtends("view", module.prototype) };
			if (info && info.style && info.style.path === styleName) {
				r.push(...this.getViewsInPageByType(module));
			}
		});
		return r;
	}

	getChangeModules(moduleNames) {
		let changeModules = [...moduleNames];
		let loadedModules = this.context.loader.moduleManager.getLoadedModules();
		Reflect.ownKeys(loadedModules).forEach(key => {
			let source = loadedModules[key].toString();
			moduleNames.forEach(file => {
				let reg = new RegExp(`require\\("${file}"\\)`);
				if (reg.test(source)) {
					if (changeModules.indexOf(key) === -1) {
						changeModules.push(key);
						this.getChangeModules([key]).forEach(n => {
							if (changeModules.indexOf(n) === -1) {
								changeModules.push(n);
							}
						});
					}
				}
			});
		});
		return changeModules;
	}

	getFutureChangedInfo(moduleNames = []) {
		let changeInfos = [];
		let context = this.context, modules = moduleNames.map(file => {
			return { file: file, clazz: this.context.loader.moduleManager.get(file) };
		});
		this.getAllModulesInPage().forEach(element => {
			let module = element[VIEWTAG];
			if (module && module.context.name === context.name) {
				modules.forEach(info => {
					if (isFunction(info.clazz) && module instanceof info.clazz) {
						changeInfos.push({
							element,
							construct: info.clazz,
							module,
							name: info.file,
						});
					}
				});
			}
		});
		return changeInfos;
	}

	resetModules(moduleNames = []) {
		this.context.loader.uninstallModules(moduleNames);
		moduleNames.forEach(file => {
			this.getViewsInModule(file).forEach(view => this.context.ddm.tags.remove(view));
		});
		return moduleNames.reduce((a, file) => {
			return a.then(() => this.context.loader.getModule(file));
		}, Promise.resolve());
	}

	resetViewStyle(view) {
		let context = this.context, ps = Promise.resolve();
		let info = { ...Metadata.getMetadataExtends("view", view.constructor.prototype) };
		info.className = info.className ? `${context.name}-${info.className}` : info.className;
		if (info.style) {
			let path = info.module.split('.');
			path.pop();
			path = path.join('.').replace(/\//g, "-");
			let id = `${context.name}-${path}:${info.className}`;
			let current = context.document.getElementById(id);
			let code = info.style;
			if (current) {
				current.parentNode.removeChild(current);
			}
			if (code.path) {
				ps = ps.then(() => context.loader.getSource(info.style.path));
			} else {
				ps = ps.then(() => code);
			}
			ps = ps.then(code => {
				excuteStyle(parseStyle(code, info.className), id, context);
			});
		}
		return ps;
	}

	resetView({ view, element }) {
		if (!view.isRemoved()) {
			this.setViewPreremoveState(view);
			element.parentNode.removeChild(element);
			if (view.getParent()) {
				return view.getParent()._rerender();
			}
		}
		return Promise.resolve();
	}

	replaceAll(files = [], map) {
		console.group(`%c[ADA-HMR] [${this.context.name}] ${this._formatData()}`, "font-weight:bold");
		let outmap = [], temps = new Set(), styles = new Set(), modules = new Set();
		files.filter(file => {
			let ismap = map[getMappedPath(file)] !== undefined;
			!ismap && outmap.push(file);
			return ismap;
		}).forEach(file => {
			let suffix = file.split(".").pop();
			if (suffix === "html") {
				temps.add(file);
			} else if (["css", "scss", "less"].indexOf(suffix) !== -1) {
				styles.add(file);
			} else {
				modules.add(file);
			}
			console.log(`%cFile [ ${file} ] changed`, "color:#3D78A7;font-weight:bold");
		});
		if (temps.size + styles.size + modules.size > 0) {
			const replaceViews = new Map();
			this.context.window.Ada.updateSourceMap(this.context.name, map);
			this.context.loader.uncacheSource([...styles, ...temps]);
			return Promise.resolve().then(() => {
				let ps = Promise.resolve();
				styles.forEach(style => {
					let targets = this.getViewsByStyleName(style);
					targets.forEach(view => {
						ps = ps.then(() => {
							const path = view.getModulePath();
							return this.resetViewStyle(view).then(() => path);
						}).then(path => {
							console.log(`%cReplace style of view [ ${path} ]`, "color:#3D78A7;font-weight:bold");
						});
					});
					targets === 0 && modules.add(style);
				});
				return ps;
			}).then(() => {
				temps.forEach(temp => {
					let targets = this.getViewsByTemplateName(temp);
					targets.forEach(view => replaceViews.set(view, view.getElement()));
					targets.length === 0 && modules.add(temp);
				});
			}).then(() => {
				let changeModules = this.getChangeModules([...modules]);
				let replaceInfos = this.getFutureChangedInfo(changeModules);
				return this.resetModules(changeModules).then(() => {
					replaceInfos.map(a => {
						let parent = a.module.getParent();
						if (parent) {
							parent && replaceViews.set(parent, parent.getElement());
						} else {
							replaceViews.set(a.module, a.module.getElement());
						}
					});
				});
			}).then(() => {
				let ps = Promise.resolve();
				replaceViews.forEach((element, view) => {
					ps = ps.then(() => {
						const path = view.getModulePath();
						return this.resetView({ view, element }).then(() => path);
					}).then(path => {
						console.log(`%cReplace views from entry [ ${path} ]`, "color:#3D78A7;font-weight:bold");
					});
				});
				return ps;
			}).then(() => {
				let ps = Promise.resolve();
				if (this.getAllModulesInPage().length === 0) {
					ps = ps.then(() => {
						console.log(`%cRerender from root`, "color:#3D78A7;font-weight:bold");
						return this.context.window.Ada.reboot();
					});
				}
				return ps.then(() => {
					console.log("%cDone.", "color:#3D78A7;font-weight:bold");
					console.groupEnd();
				});
			}).catch(e => {
				console.log(`%cError.`, "color:#FF4201;font-weight:bold");
				console.error(e);
				console.groupEnd();
			});
		} else {
			if (outmap.length > 0) {
				console.log(`%c${outmap.length > 1 ? "files" : "file"} [${outmap.join(",")}] ${outmap.length > 1 ? "are" : "is"} out map`, "color:#3D78A7;font-weight:bold");
			}
			console.log(`%cNo file updated`, "color:#3D78A7;font-weight:bold");
			console.groupEnd();
		}
	}
}

module.exports = ExtendModule;